﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

using Exif.Exceptions;
using Exif.Interfaces;
using Exif.Various;

using PostSharp.Laos;

namespace Exif.Jpeg.Base
{
	///<summary>
	///Не ThreadSafe! (знаю, где и что подправить, но - лениво...)
	///</summary>
	//[DebuggerStepThrough]
	public class ImageReader : IImageReader
	{
		#region Nested types

		///<summary>
		///При доступе в режиме последовательного чтения сохраняет и восстанавливает позицию.
		///</summary>
		[Serializable]
		//[DebuggerStepThrough]
		internal class AutoOffsetAttribute : OnMethodBoundaryAspect
		{
			public override void OnEntry(MethodExecutionEventArgs eventArgs)
			{
				var offset = (long)eventArgs.GetReadOnlyArgumentArray()[0];
				var imageData = (ImageReader)eventArgs.Instance;

				if (imageData.IsSequential)
				{
					imageData.PushPosition(offset);
				}
				else
				{
					imageData.Position = offset;
				}
			}

			public override void OnExit(MethodExecutionEventArgs eventArgs)
			{
				var imageData = (ImageReader)eventArgs.Instance;
				if (imageData.IsSequential)
				{
					imageData.PopPosition();
				}
			}
		}

		#endregion

		#region Частные поля класса

		private readonly object _syncRoot;

		private readonly byte[] _data;

		private readonly int _offset;

		private readonly EndianBinaryReader _reader;

		private int _sequentialReading;

		private readonly Stack<Endian> _endianStack;

		private readonly Stack<long> _positionStack;

		#endregion

		#region Конструктор

		internal ImageReader(byte[] data)
			: this(data, 0, data.Length)
		{
		}

		internal ImageReader(byte[] data, int offset, int length)
		{
			this._data = data;
			this._offset = offset;
			this._syncRoot = new object();

			var stream = new MemoryStream(this._data, offset, length, false, false);
			this._reader = new EndianBinaryReader(stream, Endian.Big);

			this._endianStack = new Stack<Endian>(4);
			this._positionStack = new Stack<long>(2);
		}

		#endregion

		#region Свойства класса

		///<summary>
		///Находимся ли в режиме последовательного доступа
		///</summary>
		protected bool IsSequential
		{
			get { return this._sequentialReading > 0; }
		}

		///<summary>
		///Объект синхронизации
		///</summary>
		public object SyncRoot
		{
			get { return this._syncRoot; }
		}

		///<summary>
		///Смещение от начала данных
		///</summary>
		internal long Offset
		{
			get { return this._offset; }
		}

		///<summary>
		///Размер изображения (в байтах)
		///</summary>
		public long Length
		{
			get { return this._reader.BaseStream.Length; }
		}

		///<summary>
		///Текущая позиция в потоке чтения
		///</summary>
		public long Position
		{
			get { return this._reader.BaseStream.Position; }
			private set { this._reader.BaseStream.Position = value; }
		}

		///<summary>
		///Порядок байт при чтении
		///</summary>
		public Endian Endian
		{
			get { return this._reader.Endian; }
			set { this._reader.Endian = value; }
		}

		#endregion

		#region Методы произвольного чтения данных

		///<summary>
		///Читаем два байта всегда в прямом порядке
		///</summary>
		///<param name="offset"></param>
		///<returns></returns>
		[AutoOffset]
		public UInt16 ReadCode(long offset)
		{
			return ReadCode();
		}

		///<summary>
		///Считывает <param name="count"/> количество байт
		///</summary>
		///<returns>Массив байт (длины, равной <paramref name="count"/> или меньшей, если в
		///потоке осталось меньше несчитанных байт)</returns>
		[AutoOffset]
		public byte[] ReadBytes(long offset, int count)
		{
			return ReadBytes(count);
		}

		///<summary>
		///
		///</summary>
		///<param name="offset"></param>
		///<returns></returns>
		[AutoOffset]
		public byte ReadByte(long offset)
		{
			return ReadByte();
		}

		///<summary>
		///
		///</summary>
		///<param name="offset"></param>
		///<returns></returns>
		[AutoOffset]
		public Int16 ReadInt16(long offset)
		{
			return ReadInt16();
		}

		///<summary>
		///
		///</summary>
		///<param name="offset"></param>
		///<returns></returns>
		[AutoOffset]
		public UInt16 ReadUInt16(long offset)
		{
			return ReadUInt16();
		}

		///<summary>
		///
		///</summary>
		///<param name="offset"></param>
		///<returns></returns>
		[AutoOffset]
		public Int32 ReadInt32(long offset)
		{
			return ReadInt32();
		}

		///<summary>
		///
		///</summary>
		///<returns></returns>
		[AutoOffset]
		public UInt32 ReadUInt32(long offset)
		{
			return ReadUInt32();
		}

		///<summary>
		///
		///</summary>
		///<param name="offset"></param>
		///<param name="length"></param>
		///<param name="encoding"></param>
		///<returns></returns>
		[AutoOffset]
		public string ReadString(long offset, int length, Encoding encoding)
		{
			return ReadString(length, encoding);
		}

		///<summary>
		///
		///</summary>
		///<param name="offset"></param>
		///<returns></returns>
		[AutoOffset]
		public Rational ReadRational(long offset)
		{
			return ReadRational();
		}

		///<summary>
		///
		///</summary>
		///<param name="offset"></param>
		///<returns></returns>
		[AutoOffset]
		public SRational ReadSRational(long offset)
		{
			return ReadSRational();
		}

		#endregion

		#region Методы последовательного чтения данных

		///<summary>
		///Читаем два байта всегда в прямом порядке
		///</summary>
		///<returns></returns>
		public UInt16 ReadCode()
		{
			//var buffer = this._reader.ReadBytes(2);
			//return (UInt16)((buffer[0] << 8) | buffer[1]);

			this.PushEndian(Endian.Big);
			var code = this._reader.ReadUInt16();
			this.PopEndian();
			return code;
		}

		///<summary>
		///Считывает <param name="count"/> количество байт
		///</summary>
		///<returns>Массив байт (длины, равной <paramref name="count"/> или меньшей, если в
		///потоке осталось меньше несчитанных байт)</returns>
		public byte[] ReadBytes(int count)
		{
			return this._reader.ReadBytes(count);
		}

		///<summary>
		///
		///</summary>
		///<returns></returns>
		public byte ReadByte()
		{
			return this._reader.ReadByte();
		}

		///<summary>
		///
		///</summary>
		///<returns></returns>
		public Int16 ReadInt16()
		{
			return this._reader.ReadInt16();
		}

		///<summary>
		///
		///</summary>
		///<returns></returns>
		public UInt16 ReadUInt16()
		{
			return this._reader.ReadUInt16();
		}

		///<summary>
		///
		///</summary>
		///<returns></returns>
		public Int32 ReadInt32()
		{
			return this._reader.ReadInt32();
		}

		///<summary>
		///
		///</summary>
		///<returns></returns>
		public UInt32 ReadUInt32()
		{
			return this._reader.ReadUInt32();
		}

		///<summary>
		///
		///</summary>
		///<param name="length"></param>
		///<param name="encoding"></param>
		///<returns></returns>
		public string ReadString(int length, Encoding encoding)
		{
			var bytes = _reader.ReadBytes(length);
			return encoding.GetString(bytes);
		}

		///<summary>
		///
		///</summary>
		///<returns></returns>
		public Rational ReadRational()
		{
			var numerator = _reader.ReadUInt32();
			var denominator = _reader.ReadUInt32();
			return new Rational(numerator, denominator);
		}

		///<summary>
		///
		///</summary>
		///<returns></returns>
		public SRational ReadSRational()
		{
			var numerator = _reader.ReadInt32();
			var denominator = _reader.ReadInt32();
			return new SRational(numerator, denominator);
		}

		///<summary>
		///Начинает последовательное чтение данных
		///</summary>
		///<param name="startOffset"></param>
		public void BeginRead(long startOffset)
		{
			if (this.IsSequential)
			{
				throw new SequentialModeException(true);
			}

			this._sequentialReading++;
			this.Position = startOffset;
		}

		///<summary>
		///Сдвигает текущую позицию на <paramref name="offset"/> байт.
		///</summary>
		///<param name="offset">Количество байт, на которое надо сдвинуть текущее положение</param>
		public void Advance(long offset)
		{
			this._reader.BaseStream.Position += offset;
		}

		///<summary>
		///Заканчивает последовательное чтение данных
		///</summary>
		public void EndRead()
		{
			this._sequentialReading--;
		}

		#endregion

		#region Методы класса

		///<summary>
		///Сохраняет предыдущий порядок байт и устанавливает новый
		///</summary>
		///<param name="newEndian"></param>
		public void PushEndian(Endian newEndian)
		{
			this._endianStack.Push(this.Endian);
			this.Endian = newEndian;
		}

		///<summary>
		///Устанавливает предыдущий порядок байт
		///</summary>
		public void PopEndian()
		{
			this.Endian = this._endianStack.Pop();
		}

		///<summary>
		///Сохраняет предыдущее местоположение в потоке и устанавливает новое
		///</summary>
		///<param name="newPosition">Новое положение курсора</param>
		public void PushPosition(long newPosition)
		{
			this._positionStack.Push(this.Position);
			this.Position = newPosition;
		}

		///<summary>
		///Восстанавливает предыдущее положение курсора
		///</summary>
		public void PopPosition()
		{
			this.Position = this._positionStack.Pop();
		}

		#endregion
	}
}